package BNS.coloring;

import java.awt.Point;
import java.io.File;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.Date;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;

import BNS.graphs.ColorMap;
import BNS.graphs.Graph;
import BNS.graphs.GraphPack;
import BNS.graphs.GraphPanel;
import BNS.graphs.Node;
import BNS.plumbing.BNSGameController;
import BNS.plumbing.BNSPersona;
import BNS.plumbing.ClientLiaison;
import BNS.plumbing.Library;
import BNS.plumbing.Manager;


/** This is an example class that implements a simple graph coloring game.
	The game is solved when all players have colored their node with a color different 
	from each of their neighbours. 
	It assumes that the colors are integers in {1,2, ..., colorChoices}. 
	The counterpart class in the client machine is an BNSPersona known as ColoringPersona.
	*/
public class ColoringGame extends BNSGameController {
	protected int numClients, nEvents;
//	protected int[] state;
	protected NodeState[] nodeStates;	// stuff about nodes, indexed by Node.getIndex()
	private NodeState[][] statelets;
	protected int colorChoices;
	protected boolean colormapIsIdentity, showNodes;
	protected float[] payOff;
	protected String[] payOffStr;
	protected int currentProgress, perfection;
	protected static final long serialVersionUID = 01L;
	protected String payoffDefn;
	//protected int specialColor;
	protected Vector<Float> bonus;
	
	/** Used to construct a game, using parameters from a Tokenizer.
		Conventionally, this (and the constructor) are used to build an
		object to be stored in a file and used in a session later.
		*/
	public static BNSGameController build(String dir, StringTokenizer st) {
		BNSGameController game= null;
		try {
			File gpFile= new File(dir,st.nextToken()+".gpack");
			GraphPack gp= Library.getPack(gpFile);
			if (gp==null) return null;
			int time= Integer.parseInt(st.nextToken());
			int colors= Integer.parseInt(st.nextToken());
			float phatPay= Float.parseFloat(st.nextToken());
			System.err.println("parameters: "+ time +", "+ phatPay);
			game= new BNS.coloring.ColoringGame(gp,colors,time,phatPay);
		} catch(Exception e) { System.err.println("problem: "+ e); }
		return game;
		}
	
	/** sole constructor
		@param gp the GraphPack to use in this game
		@param colors the number of different colors to use
		@param time the maximum number of seconds the game will be allowed to run
		@param winPayoff the dollar reward for success
		*/
	public ColoringGame(GraphPack gp, int colors, int time, float pay,
	        Vector<Float> bonus, int ident) {
		super(time);	// timeLimit
		grpk= gp;
		numClients= grpk.size();
		colorChoices= colors;
		perfection= grpk.graph.numEdges();
		showNodes= true;
		otherInits(pay,bonus,ident);
		}
	
	public ColoringGame(GraphPack gp, int colors, int time, float highPay) {
	  this(gp,colors,time,highPay,new Vector<Float>(),0);
	}
	
	/** These inits may be different in subclasses. */	
	protected void otherInits(float pay, Vector<Float> bonus, int ident) {
		colormapIsIdentity= ident<=0;
		payOff= new float[]{0,pay};
		payOffStr= new String[2];
		//specialColor=special;
		this.bonus=bonus;
		for (int i=0; i<payOff.length; i++) {
			payOffStr[i]= String.format("$%.2f",payOff[i]);
//			System.err.println(" "+ payOffStr[i]);
			}
		//if(pay<=0) 
		payoffDefn="If the session ends successfully, you will earn "+payOffStr[1]+".";
		//else payoffDefn="If everyones color is different from their neighbors, " +
		//		"and your color is ";
		}
	
	/** returns a string defining how dollar rewards are awarded. 
		(Not part of BNSGameController.)  */
	public String getPayoffDefn(Node node) { return payoffDefn; }
	
	/** a routine that gets called at run time, to finish setting up
		things that cannot be initialized in the constructor
		(viz. the constructor should not create any GUI elements.)
		@param host	the Session Host, which can be queried for information 
			about the clients, players, etc. that are assigned to this game.
		@param locn the recommended pixel location for the top left 
			corner of the monitor GUI. */	
	public void init(Manager mgr, Point locn, PrintStream sessionLog, PrintStream gameLog) {
		super.init(mgr,locn,sessionLog,gameLog);
		numClients= grpk.size();
		GraphPanel gPanel= new GraphPanel(ColorMap.identity());
		monitor= new Monitor(gPanel,locn);
		gPanel.init(grpk.graph);
		this.sessionLog= sessionLog;
		this.gameLog= gameLog;
		sessionLog.println(getTitle()+"  "+ new Date());
		grpk.graph.prettyPrint(sessionLog);
		sessionLog.println("number of color choices: "+ colorChoices);
		suspend();
		gameValue= "init'ed";
		nEvents= 0;
		
		nodeStates= new NodeState[numClients];
		for (int nsi=0; nsi<numClients; nsi++) {
			int initColor= 0;	// elaborate this!
			nodeStates[nsi]= new NodeState(nsi,initColor);
			}
		statelets= new NodeState[numClients][];
		for (int nsi=0; nsi<numClients; nsi++) {
			Graph graphlet= grpk.graphlets[nsi];
			statelets[nsi]= new NodeState[graphlet.numNodes()];
			Enumeration<Node> enn= graphlet.enumNodes();
			int eNumlet= 0;
			while (enn.hasMoreElements()) {
				Node n= enn.nextElement();
				statelets[nsi][eNumlet++]= nodeStates[n.getIndex()];
				}
			assert (eNumlet==statelets[nsi].length);
			}
		System.err.println(payoffDefn);
		}
	
	public int getSize() { return grpk.size(); }
	
	public String getTitle() { return "Session"; }
	
	public String getInfo() {
		return getTitle() +" of size "+ getSize() +"\n"
			+" number of colours allowed= "+ colorChoices +"\n"
			+" losePayoff="+ payOff[0] +" winPayoff="+ payOff[1] +"\n"
			+" bonus Vector="+bonusToString()+"\n"
			+" time allowed="+ timeLimit +"\n"
			+" graphpack info= "+ grpk.getInfo();
			}
			
	private String bonusToString() {
	  String ret="";
	  for(int i=0;i<colorChoices;i++) {
	    if(bonus.size()>i) ret+=bonus.get(i)+", ";
	  }
	  if(!ret.equals("")) ret=ret.substring(0,ret.length()-2);
	  return ret; 
	}
	
	/** The method that implements player actions in this graph coloring game.
		It returns true if the game is not over, the game is not paused, the
		clientID looks legitimate, and the color is in {0, 1, 2}. Otherwise it
		returns false to let the ColoringPersona know that its action failed.
		<p>
		The game is solved when all players have colored their node the same
		color as everyone else. As soon as that happens the game is declared over
		by this method calling finished().
		</p><p>
		This alter method is one that is not mentioned anywhere in this API
		because the designer is completely free to design it (and others) as he
		wishes. The only object that will use it is an implementation of BNSPersona
		that he also designs to match it.
		</p><p>
		This method must do its own parameter sanity checking, since the SessionHost
		and the BNSClient do not understand what they mean to particular games. */
	public boolean alter(ClientLiaison client, Serializable changeObj) {
		//System.err.println(" client #"+ clientID+" changing  to "+value);
		long tstamp= clock.getElapsedTime();
		if (client==null) System.err.println("client==null !!");
		if (client.eigenNode==null) System.err.println("eigenNode==null !!");
		if (changeObj instanceof Integer) 
			return changeColor(tstamp, client, (Integer)changeObj);
		else return false;
		}
		
	private boolean changeColor(long tstamp, ClientLiaison client, int value) {
		if (value<1 || value>=GraphPanel.swatches.length) {
			System.err.println(tstamp +" client "+ client
							   +" attempted setting to wonky value "+ value);
			return false;
			}
		Node node= client.eigenNode;
		//System.err.println(" event #"+ nEvents+": node "+ node +" changing  to "+value);
		//System.err.println(" (game pause "+gamePaused+", complete "+gameCompleted +")");
		synchronized(nodeStates) {	// only this section needs to be threadsafe
			if (clock.suspended() || gameCompleted) return false;
			nEvents++;
			gameLog.println(tstamp+" "+client.clientID+" "+ node +" "+value);
//			System.err.println(" node #"+ node +" changed  to "+value);
//			node.color= value;	Does note update the right graph
			grpk.graph.getNode(node.getIndex()).color= value;
			nodeStates[node.getIndex()].colorInt= value;	// known to be legitimate value
			currentProgress= getProgress();
			if (currentProgress==perfection/*solved()*/) {
				finished("game solved",0); gameValue= "solved"; gameCompleted= true;
				System.out.println("game solved");
				}
			}
		monitor.repaint();
		return true;
		}
	
	/** returns a flag indicating if the graph has been well-colored globally */
	protected boolean solved() { return grpk.graph.colored(true); }
	
	/** returns a flag indicating if the graphlet has been locally well-colored */
//	protected boolean locallySolved(Graph grlet) { return grlet.locallySolved(true,state); }
	
	/** returns a measure of "social welfare" indicating how the group as a whole is doing */
	protected int getProgress() { return grpk.graph.satEdges(true); }
	
//	public java.io.Serializable getUpdate(int clientID) {
//		Node node= host.nodeLookup(clientID);
//		return state;	// lazy server just returns the whole graph state.
//		// Some day it may be important to tighten this up.
//		}
	
	/** called by ClientLiaison on behalf of Client to get data relevant to a node. 
		@param nodeID the identifying index of the node in the game graph.
		@return an Envelope, which ColoringPersona must interpret as the 
		colors of each of the nodes in the graph. */
	public ClientLiaison.StatusCarrier getUpdate(int nodeID) { // TODO: synchronized
		Envelope env= new Envelope();
		env.elapsedTime= clock.getElapsedTime();
		env.currentProgress= currentProgress;		
//		updateCumPay(env.elapsedTime, nodeID);
		// The clientStates datastructure is fixed, but the data in it is updated by calls 
		// to changeColor. Here we simply return the array that holds the data.
		env.localStates= statelets[nodeID];
		return env;
		}
	
	/** method required by BNSGameController interface.
		@param clientID	an int identifying which client the Persona will be for.
		@return the BNSPersona object that runs in the client to implement
		the player's GUI of this game. */
	public BNSPersona getPersona(ClientLiaison client, Node node) {
		System.err.println("creating new ColoringPersona("+ node +") for "+ client);
		int nodeIdx= node.getIndex();
		nodeStates[nodeIdx].client= client;
		return new ColoringPersona(client, grpk.graphlets[nodeIdx],
							   colorChoices, timeLimit, perfection,
							   colormapIsIdentity, showNodes, 
							   getPayoffDefn(node), payOffStr, bonus);
		}
	
	/** Goes through all clients, calculates their final payoff, and sends a message
		to each client reporting same.	*/
	public void showPayoffs(int time) {
		float gamePay= 0.0f;
		gameLog.print("earn");
		for (int nsi= 0; nsi<nodeStates.length; nsi++) {
			ClientLiaison client= nodeStates[nsi].client;
//			if (client==null || client.game!=gameController || client.empty) continue;
			float pay= getPayoff(client.eigenNode.getIndex(), time);
			gamePay+= pay;
			client.sendMessage(String.format("DONE: your final payoff is $%.2f", pay));
			gameLog.format(" "+client.playerName+"=%f",pay);
			}
		gameLog.format(" total=%f%n",gamePay);
		}
	
	public float getPayoff(int nodeIdx, int time) { 
		boolean flag= currentProgress==perfection;
		System.err.println("node ("+ nodeIdx +")"+(flag?" ":" not ")+"solved");
		float extra=bonus.size()>=nodeStates[nodeIdx].colorInt?bonus.get(nodeStates[nodeIdx].colorInt-1):0;
		System.err.println(extra);
		System.err.println(nodeStates[nodeIdx].colorInt);
		return flag? payOff[1]+extra:payOff[0];
		}
	
	/** returns an int indicating the strategic status of a given node (player).
		@param clientID the identifier for the calling client
		@return 1 if graphlet is locally well-colored, 0 otherwise */
/*	public int getWinStatus(int clientID) { 
		Node node= host.nodeLookup(clientID);
		boolean flag= locallySolved(grpk.graphlets[node.getIndex()]);
		return flag?1:0;
		}
*/		
	public int monitorUpdate(Monitor mon) {
		//if (marketStuck) mon.welfareDisplay.setText("FINISHED!");
		//else mon.welfareDisplay.setText("active");
		mon.welfareDisplay.setText(gameValue);
		mon.timeDisplay.setText(String.format(" %d ",clock.getElapsedTime()/1000));
		return 300;
		}
	
	} // EOC ColoringGame
